from datetime import datetime, timedelta
from typing import Optional
from jose import JWTError, jwt
from passlib.context import CryptContext
from fastapi import Depends, HTTPException, status, Request
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from sqlalchemy.orm import Session
from app.config import settings
from app.database import get_db
from app.redis_client import get_redis
from app import models
import secrets
import hashlib

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
security = HTTPBearer()


def verify_password(plain_password: str, hashed_password: str) -> bool:
    """验证密码"""
    if not plain_password:
        return False
    
    # bcrypt 只使用前72字节，所以验证时也只取前72字节
    if len(plain_password.encode('utf-8')) > 72:
        plain_password = plain_password.encode('utf-8')[:72].decode('utf-8', errors='ignore')
    
    return pwd_context.verify(plain_password, hashed_password)


def get_password_hash(password: str) -> str:
    """生成密码哈希"""
    if not password:
        raise ValueError("密码不能为空")
    
    # bcrypt 限制密码最大长度为72字节，超过部分会被忽略
    # 为了安全，我们主动截断并提示
    if len(password.encode('utf-8')) > 72:
        # 截断到72字节
        password = password.encode('utf-8')[:72].decode('utf-8', errors='ignore')
    
    return pwd_context.hash(password)


def generate_random_password(length: int = 8) -> str:
    """生成随机密码（最大72字节）"""
    import string
    import random
    
    # 限制长度，避免超过 bcrypt 的72字节限制
    if length > 72:
        length = 72
    
    chars = string.ascii_letters + string.digits
    return ''.join(random.choice(chars) for _ in range(length))


def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
    """创建访问令牌"""
    to_encode = data.copy()
    
    # JWT 的 sub 字段必须是字符串
    if "sub" in to_encode and not isinstance(to_encode["sub"], str):
        to_encode["sub"] = str(to_encode["sub"])
    
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(hours=settings.ACCESS_TOKEN_EXPIRE_HOURS)
    
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
    return encoded_jwt


def verify_token(token: str) -> Optional[dict]:
    """验证令牌"""
    import logging
    logger = logging.getLogger(__name__)
    
    try:
        payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
        logger.info(f"Token 验证成功，payload: {payload}")
        return payload
    except JWTError as e:
        logger.error(f"Token 验证失败: {type(e).__name__}: {str(e)}")
        logger.error(f"SECRET_KEY 长度: {len(settings.SECRET_KEY)}")
        logger.error(f"ALGORITHM: {settings.ALGORITHM}")
        return None


def save_token_to_redis(user_id: int, token: str):
    """保存token到Redis"""
    redis = get_redis()
    key = f"token:{user_id}"
    redis.setex(key, settings.ACCESS_TOKEN_EXPIRE_HOURS * 3600, token)


def check_token_in_redis(user_id: int, token: str) -> bool:
    """检查token是否在Redis中"""
    import logging
    logger = logging.getLogger(__name__)
    
    redis = get_redis()
    key = f"token:{user_id}"
    stored_token = redis.get(key)
    
    logger.info(f"Redis 查询 key: {key}")
    logger.info(f"Redis 返回类型: {type(stored_token)}, 值: {stored_token}")
    
    # Redis 返回的是 bytes，需要解码为字符串
    if stored_token is None:
        logger.warning(f"Redis 中没有找到 token，key: {key}")
        return False
    
    if isinstance(stored_token, bytes):
        stored_token = stored_token.decode('utf-8')
        logger.info(f"解码后的 stored_token: {stored_token[:20]}...")
    
    token_match = stored_token == token
    logger.info(f"Token 比较结果: {token_match}")
    
    return token_match


def delete_token_from_redis(user_id: int):
    """从Redis删除token"""
    redis = get_redis()
    key = f"token:{user_id}"
    redis.delete(key)


def get_token_from_header(request: Request) -> str:
    """从请求头或查询参数获取 token（支持 SSE）"""
    # 优先从 header 获取
    token = request.headers.get("token", "")
    
    # 如果 header 中没有，尝试从 query 参数获取（用于 EventSource）
    if not token:
        token = request.query_params.get("token", "")
    
    return token


async def get_current_user(
    token: str = Depends(get_token_from_header),
    db: Session = Depends(get_db)
) -> models.User:
    """获取当前用户（从请求头 token 字段获取）"""
    import logging
    logger = logging.getLogger(__name__)
    
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="无效的认证凭证",
        headers={"WWW-Authenticate": "Bearer"},
    )
    
    if not token:
        logger.warning("未提供 token")
        raise credentials_exception
    
    logger.info(f"收到 token: {token[:20]}...")
    
    payload = verify_token(token)
    
    if payload is None:
        logger.warning("token 验证失败")
        raise credentials_exception
    
    user_id_str = payload.get("sub")
    if user_id_str is None:
        logger.warning("token 中没有 user_id")
        raise credentials_exception
    
    # 将字符串 user_id 转换为整数
    try:
        user_id: int = int(user_id_str)
    except (ValueError, TypeError):
        logger.warning(f"user_id 格式错误: {user_id_str}")
        raise credentials_exception
    
    logger.info(f"token 解析出 user_id: {user_id}")
    
    # 检查token是否在Redis中
    if not check_token_in_redis(user_id, token):
        logger.warning(f"token 在 Redis 中不存在或不匹配，user_id: {user_id}")
        raise credentials_exception
    
    logger.info(f"Redis 验证通过，user_id: {user_id}")
    
    user = db.query(models.User).filter(models.User.id == user_id).first()
    if user is None:
        logger.warning(f"用户不存在，user_id: {user_id}")
        raise credentials_exception
    
    if user.status != "active":
        logger.warning(f"用户已被禁用，user_id: {user_id}")
        raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="用户已被禁用")
    
    logger.info(f"用户验证成功: {user.phone}")
    return user


def generate_sms_code() -> str:
    """生成6位数字验证码（100000-999999）"""
    import random
    return str(random.randint(100000, 999999))


def verify_sms_code(plain_code: str, stored_code: str) -> bool:
    """验证短信验证码（直接比较）"""
    return plain_code == stored_code

